<template>
  <span>
    {{ displayValue }}
  </span>
</template>

<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'

const props = defineProps({
  startVal: {
    type: Number,
    default: 0
  },
  endVal: {
    type: Number,
    default: 2017
  },
  duration: {
    type: Number,
    default: 3000
  },
  autoplay: {
    type: Boolean,
    default: true
  },
  decimals: {
    type: Number,
    default: 0,
    validator(value) {
      return value >= 0
    }
  },
  decimal: {
    type: String,
    default: '.'
  },
  separator: {
    type: String,
    default: ','
  },
  prefix: {
    type: String,
    default: ''
  },
  suffix: {
    type: String,
    default: ''
  },
  useEasing: {
    type: Boolean,
    default: true
  },
  easingFn: {
    type: Function,
    default(t, b, c, d) {
      return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b
    }
  }
})

const emit = defineEmits(['mountedCallback', 'callback'])

const localStartVal = ref(props.startVal)
const displayValue = ref(formatNumber(props.startVal))
const printVal = ref(null)
const paused = ref(false)
const localDuration = ref(props.duration)
const startTime = ref(null)
const timestampRef = ref(null)
const remaining = ref(null)
let rAF = null

const countDown = computed(() => props.startVal > props.endVal)

watch(() => props.startVal, () => {
  if (props.autoplay) {
    start()
  }
})

watch(() => props.endVal, () => {
  if (props.autoplay) {
    start()
  }
})

onMounted(() => {
  if (props.autoplay) {
    start()
  }
  emit('mountedCallback')
})

onUnmounted(() => {
  cancelAnimationFrame(rAF)
})

function start() {
  localStartVal.value = props.startVal
  startTime.value = null
  localDuration.value = props.duration
  paused.value = false
  rAF = requestAnimationFrame(count)
}

function pauseResume() {
  if (paused.value) {
    resume()
    paused.value = false
  } else {
    pause()
    paused.value = true
  }
}

function pause() {
  cancelAnimationFrame(rAF)
}

function resume() {
  startTime.value = null
  localDuration.value = remaining.value
  localStartVal.value = printVal.value
  rAF = requestAnimationFrame(count)
}

function reset() {
  startTime.value = null
  cancelAnimationFrame(rAF)
  displayValue.value = formatNumber(props.startVal)
}

function count(timestamp) {
  if (!startTime.value) startTime.value = timestamp
  timestampRef.value = timestamp
  const progress = timestamp - startTime.value
  remaining.value = localDuration.value - progress

  if (props.useEasing) {
    if (countDown.value) {
      printVal.value = localStartVal.value - props.easingFn(progress, 0, localStartVal.value - props.endVal, localDuration.value)
    } else {
      printVal.value = props.easingFn(progress, localStartVal.value, props.endVal - localStartVal.value, localDuration.value)
    }
  } else {
    if (countDown.value) {
      printVal.value = localStartVal.value - ((localStartVal.value - props.endVal) * (progress / localDuration.value))
    } else {
      printVal.value = localStartVal.value + (props.endVal - localStartVal.value) * (progress / localDuration.value)
    }
  }

  if (countDown.value) {
    printVal.value = printVal.value < props.endVal ? props.endVal : printVal.value
  } else {
    printVal.value = printVal.value > props.endVal ? props.endVal : printVal.value
  }

  displayValue.value = formatNumber(printVal.value)
  if (progress < localDuration.value) {
    rAF = requestAnimationFrame(count)
  } else {
    emit('callback')
  }
}

function isNumber(val) {
  return !isNaN(parseFloat(val))
}

function formatNumber(num) {
  num = Number(num).toFixed(props.decimals)
  num += ''
  const x = num.split('.')
  let x1 = x[0]
  const x2 = x.length > 1 ? props.decimal + x[1] : ''
  const rgx = /(\d+)(\d{3})/
  if (props.separator && !isNumber(props.separator)) {
    while (rgx.test(x1)) {
      x1 = x1.replace(rgx, '$1' + props.separator + '$2')
    }
  }
  return props.prefix + x1 + x2 + props.suffix
}

defineExpose({
  start,
  pauseResume,
  pause,
  resume,
  reset
})
</script>
